/*
 *   Time Tracking Web Application
 *   Copyright (C) 2008, Nick Griffiths
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *   
 *   Contact nicobrevin at gmail dot com
 *   http://code.google.com/p/openharvest/
 */

package org.brevs.time.action;

import java.io.StringWriter;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.appfuse.model.User;
import org.appfuse.webapp.action.BaseAction;
import org.brevs.time.action.beans.Dateinfo;
import org.brevs.time.action.beans.EntryBean;
import org.brevs.time.manager.TimeManager;
import org.brevs.time.model.Entry;
import org.brevs.time.model.Project;
import org.brevs.time.model.ProjectCategory;
import org.json.JSONException;
import org.json.JSONWriter;
import org.springframework.security.context.SecurityContext;
import org.springframework.security.context.SecurityContextHolder;

import com.opensymphony.xwork2.Preparable;

/**
 * Action containing methods for manipulating a day of timer entries. 
 * @author nick
 */
public class DayOfEntriesAction extends BaseAction implements Preparable {

	private static final long serialVersionUID = -2666037435814335694L;

	private TimeManager timeManager;
	
	private List<EntryBean> todays;
	
	private EntryBean entry;

	private List<Project> projects;

	private List<ProjectCategory> categories;

	private String categoryJson;
	
	private Dateinfo dateinfo;

	//---- Injected
	
	public void setTimeManager(TimeManager timeManager) {
		this.timeManager = timeManager;
	}
	
	//---- Model
	
	public List<ProjectCategory> getCategories() {
		return categories;
	}
	
	public String getCategoryJson() {
		return categoryJson;
	}

	public List<EntryBean> getTodays() {
		return todays;
	}

	public List<Project> getProjects() {
		return projects;
	}

	public void setEntry(EntryBean entry) {
		this.entry = entry;
	}
	
	public EntryBean getEntry() {
		return entry;
	}
	
	public Dateinfo getDateinfo() {
		return dateinfo;
	}
	
	//---- Action Methods
	
	public String today() {
		return SUCCESS;
	}
	
	/**
	 * Adds a new entry for the user, and starts it timing if the user hasn't 
	 * inputed an initial time value.
	 * @return
	 */
	public String addEntry() {
		Entry entry = this.entry.toEntry(timeManager);
		entry.setStartDate(getDay());
		User user = getActiveUser();
		entry.setUser(user);

		timeManager.saveEntry(entry);
		
		if (entry.getSeconds() == 0) {
			timeManager.startTiming(entry);
		}
		
		populate();
		return dateinfo.isToday() ? SUCCESS : "anotherday";
	}
	
	/**
	 * Updates an entry from a form to the database.
	 * @return
	 */
	public String updateTimer() {
		Entry entry = this.entry.toEntry(timeManager);
		timeManager.saveEntry(entry);
		
		populate();
		return SUCCESS;
	}
	
	/**
	 * Starts off a timer.
	 * @return
	 */
	public String startTimer() {
		Entry entry = getBackingEntry();
		timeManager.startTiming(entry);
		
		populate();
		return SUCCESS;
	}
	
	/**
	 * Stops a timer.
	 * @return
	 */
	public String stopTimer() {
		Entry entry = getBackingEntry();
		timeManager.stopTiming(entry);
		
		populate();
		return SUCCESS;
	}
	
	/**
	 * Put some demo data in the database.
	 * @return
	 */
	public String init() {
		timeManager.createDemoData();
		return SUCCESS;
	}
	
	/**
	 * Returns an {@link Entry}, either straight from the database, or if the 
	 * request is an edit, then the form bean is bound to an entry pulled 
	 * straight from the database.
	 * @return
	 */
	private Entry getBackingEntry() {
		return isEdit() 
		? this.entry.toEntry(timeManager) : timeManager.getEntry(this.entry.getId());
	}

	/**
	 * @return <code>true</code> if the request has the edited parameter set to
	 * true
	 */
	private boolean isEdit() {
		return "true".equals(getRequest().getParameter("edited"));
	}

	/**
	 * Populates the action with some beans for rendering the page.
	 */
	protected void populate() {
		
		this.todays = convertEntriesToBeans(timeManager.getEntries(getDay(), getActiveUser()));
		this.projects = timeManager.getProjects(getActiveUser());
		try {
			this.categoryJson = serialiseCategoriesToJson();
		} catch (JSONException e) {
			throw new RuntimeException("error creating json", e);
		}
		this.categories = new ArrayList<ProjectCategory>();
		for (Project project : this.projects) {
			this.categories.addAll(project.getCategories());
		}
		this.dateinfo = new Dateinfo(getDay());
	}
	
	protected Date getDay() {
		String timestamp = getRequest().getParameter("day");
		Date day;
		if (StringUtils.isNotEmpty(timestamp)) {
			try {
				day = Dateinfo.parseTimestamp(timestamp);
			} catch (ParseException e) {
				throw new RuntimeException(e);
			}
		} else {
			day = new Date();
		}
		return day;
	}

	/**
	 * Turns a {@link List} of {@link Entry} objects into {@link EntryBean}s, suited
	 * to life in a JSP.
	 * @param todaysEntries
	 * @return
	 */
	private List<EntryBean> convertEntriesToBeans(List<Entry> todaysEntries) {
		List<EntryBean> beans = new ArrayList<EntryBean>(todaysEntries.size());
		for (Entry entry : todaysEntries) {
			beans.add(EntryBean.create(entry));
		}
		return beans;
	}

	/**
	 * Gets all the {@link Project}s from the db, and serialises all their 
	 * {@link ProjectCategory} objects in to JSON.
	 * @return
	 * @throws JSONException
	 */
	private String serialiseCategoriesToJson() throws JSONException {
		StringWriter writer = new StringWriter();
		JSONWriter jsonWriter = new JSONWriter(writer);
		jsonWriter.object();
		
		Iterator<Project> projectIter = this.projects.iterator();
		while (projectIter.hasNext()) {
			Project project = (Project) projectIter.next();
			jsonWriter.key(project.getId().toString());
			
			Set<ProjectCategory> categories = project.getCategories();
			jsonWriter.array();
			for (ProjectCategory projectCategory : categories) {
				serialiseCategoryToJson(jsonWriter, projectCategory);
			}
			jsonWriter.endArray();
		}
		
		jsonWriter.endObject();
		return writer.getBuffer().toString();
	}

	private void serialiseCategoryToJson(JSONWriter jsonWriter, ProjectCategory category) throws JSONException {
		jsonWriter.object();
		jsonWriter.key("id");
		jsonWriter.value(category.getId());
		
		jsonWriter.key("name");
		jsonWriter.value(category.getName());
		jsonWriter.endObject();
	}

	/**
	 * Returns the {@link User} to which this request is bound.
	 * @return
	 */
	private User getActiveUser() {
		SecurityContext sc = SecurityContextHolder.getContext();
		return (User) sc.getAuthentication().getPrincipal();
	}

	//---- Preparable
	
	public void prepare() throws Exception {
		populate();
	}
}
